#include <cliArgParse.h>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <log.h>
#include <utils.h>

namespace cli {
    Interface::Interface(const char* program_name, const char* description)
    {
        if (program_name) program_name_ = program_name;
        if (description) description_ = description;
    }

    bool Interface::name_exists(const char* long_name, const char* short_name) const
    {
        if (long_name && (*long_name != EOFS)) {
            if (long_name_map_.find(long_name) != long_name_map_.end())
            {
                LOG_ERROR("Duplicate long name: %s", long_name);
                return true;
            }
        }

        if (short_name && (*short_name != EOFS))
        {
            if (short_name_map_.find(short_name) != short_name_map_.end())
            {
                LOG_ERROR("Duplicate short name: %s", short_name);
                return true;
            }
        }

        return false;
    }

    STATUS Interface::add_param(const ParamConfig* param)
    {
        if (param == nullptr)
        {
            LOG_ERROR("Parameter is null invalid");
            return RET_ERROR;
        }

        if ((!param->long_name ||  (*param->long_name == EOFS)) &&
            (!param->short_name || (*param->short_name == EOFS)))
        {
            LOG_ERROR("Parameter must have at least a long or short name");
            return RET_ERROR;
        }

        if (name_exists(param->long_name, param->short_name))
        {
            LOG_ERROR("Parameter name already exists: %s", param->long_name);
            return RET_ERROR;
        }

        params_.push_back(param);

        if (param->long_name && (*param->long_name != EOFS))
        {
            long_name_map_[param->long_name] = param;
        }
        if (param->short_name && (*param->short_name != EOFS))
        {
            short_name_map_[param->short_name] = param;
        }

        return OK;
    }
    STATUS Interface::add_params(const std::vector<ParamConfig>& params)
    {
        for (const auto& param : params)
        {
            if (add_param(&param) != OK)
            {
                return RET_ERROR;
            }
        }
        addDefaultCommands();
        return OK;
    }

    STATUS Interface::addDefaultCommands()
    {
        static ParamConfig help_params =
        {
            "help",
            "h",
            "Show this help information",
            false,
            NULLSTR,
            false,
            nullptr
        };

        if (!name_exists(help_params.long_name, help_params.short_name))
        {
            (void)add_param(&help_params);
        }
        return OK;
    }

    STATUS Interface::parse(const std::vector<std::string>& argv, Args& args)
    {
        auto funcParamParse = [=](bool isLongNameParse,
                                size_t& curIdx,
                                std::map<std::string,const ParamConfig*>& params, 
                                const std::vector<std::string>& argv,
                                Args& args) ->STATUS
            {

                const std::string& arg = argv[curIdx];
                std::string propmt = isLongNameParse ? "--" : "-";
                std::string name = isLongNameParse? arg.substr(2) : arg.substr(1);
     
                if (name.empty())
                { 
                    lastErrorMsg = utils::format("Invalid parameter format: %s", propmt.c_str());
                    return RET_ERROR;
                }

                auto it = params.find(name);
                if (it == params.end())
                {
                    lastErrorMsg = utils::format("Unknown parameter: %s%s", propmt.c_str(), name.c_str());
                    return RET_ERROR;
                }
                const ParamConfig* param = it->second;

                if (!param->takes_value)
                {
                    args.set(param->long_name, "1");
                    return OK;
                }

                if (curIdx + 1 >= argv.size())
                {
                    lastErrorMsg = utils::format("Parameter %s%s requires a value", propmt.c_str(), name.c_str());
                    return RET_ERROR;
                }

                if (param->validator && !param->validator(argv[curIdx + 1].c_str()))
                {
                    lastErrorMsg = utils::format("Invalid value for %s%s: %s",
                        propmt.c_str(), name.c_str(), argv[curIdx + 1].c_str());
                    return RET_ERROR;
                }

                args.set(param->long_name, argv[curIdx + 1]);
                
                curIdx++;

                return OK;
            };

        for (size_t idx = 0;  idx < argv.size(); ++idx)
        {
            const std::string& arg = argv[idx];

            if ((arg.size() >= 2) && (arg[0] == '-') && (arg[1] == '-'))
            {           
                COND_CHECK_RET_ERROR(funcParamParse(true, idx, long_name_map_, argv, args) == RET_ERROR);
            }
            else if ((arg.size() >= 1) && (arg[0] == '-'))
            {
                COND_CHECK_RET_ERROR(funcParamParse(false, idx, short_name_map_, argv, args) == RET_ERROR);
            }
            else
            {
                lastErrorMsg = utils::format("Invalid parameter format: %s", arg.c_str());
                return RET_ERROR;
            }
        }

        if (args.has("help")) return OK;

        for (const auto& param : params_)
        {
            if (param->default_val && (*param->default_val != EOFS) && !args.has(param->long_name))
            {
                args.set_default(param->long_name, param->default_val);
            }

            if (param->required && !args.has(param->long_name))
            {
                if ((param->short_name != nullptr) && (*param->short_name != EOFS))
                {
                    lastErrorMsg = utils::format("Missing required parameter: --%s (-%s)", param->long_name, param->short_name);
                }
                else
                {
                    lastErrorMsg = utils::format("Missing required parameter: --%s", param->long_name);
                }
                return RET_ERROR;
            }
        }

        return OK;
    }

    STATUS Interface::print_help(std::ostringstream& oss) const
    {
        oss << program_name_ << " - " << description_ << std::endl << std::endl;
        oss << "Usage: " << program_name_ << " [options]" << std::endl << std::endl;
        oss << "Options:" << std::endl;

        for (const auto &param : params_)
        {
            if (param->short_name && (*param->short_name != EOFS))
            {
                oss << "  -" << param->short_name << ", --" << param->long_name;
            }
            else
            {
                oss << "      --" << param->long_name;
            }

            if (param->takes_value)
            {
                oss << " <value>";
            }

            if (param->required)
            {
                oss << " (required)";
            }

            oss << std::endl;

            if (param->help && (*param->help != EOFS))
            {
                oss << "          " << param->help;

                if (param->default_val && (*param->default_val != EOFS))
                {
                    oss << " (default: " << param->default_val << ")";
                }

                oss << std::endl;
            }
        }
        return OK;
    }
    const std::string& Interface::getLastError() const
    {
        return lastErrorMsg;
    }
}
